#include "Types.h"
#include "Env.h"
#include "Util.h"
#include "Exception.h"

#include <stdexcept>
#include <algorithm>

namespace Lisp
{
  ValPtr ValType::Eval(EnvPtr env)
  {
    return ValPtr(this); // this->shared_from_this();
  }

  bool ValType::IsTrue() const
  {
    return this != NilVal().get();
  }

  ValPtr ValType::Meta() const
  {
    return m_meta.get() == nullptr ? NilVal() : m_meta;
  }

  ValPtr ValType::WithMeta(ValPtr meta) const
  {
    return DoWithMeta(meta);
  }

  bool ValType::Equals(ValType *val) const
  {
    return this == val;
  }

  std::string ConstantType::ToString(bool readably) const
  {
    return m_value;
  }

  bool ConstantType::IsTrue() const
  {
    return this != NilVal().get() && this != FalseVal().get();
  }

  std::string IntegerType::ToString(bool readably) const
  {
    std::string res = std::to_string(m_value);
    return res;
  }

  bool IntegerType::Equals(ValType *val) const
  {
    if (auto *integer = dynamic_cast<IntegerType *>(val))
    {
      return m_value == integer->m_value;
    }
    return false;
  }

  ValPtr IntegerType::Eval(EnvPtr env)
  {
    return ValPtr(new IntegerType(m_value));
  }

  bool IntegerType::IsTrue() const
  {
    return m_value != 0;
  }

  std::string StringBaseType::ToString(bool readably) const
  {
    return Value();
  }

  bool StringBaseType::Equals(ValType *val) const
  {
    if (auto *str = dynamic_cast<StringBaseType *>(val))
    {
      return Value() == str->Value();
    }
    return false;
  }

  std::string SymbolType::ToString(bool readably) const
  {
    return Value();
  }

  bool SymbolType::Equals(ValType *val) const
  {
    if (auto *symbol = dynamic_cast<SymbolType *>(val))
    {
      return Value() == symbol->Value();
    }
    return false;
  }

  ValPtr SymbolType::Eval(EnvPtr env)
  {
    return env->Get(Value());
  }

  std::string StringType::ToString(bool readably) const
  {
    return readably ? EscapedValue() : Value();
  }

  bool StringType::Equals(ValType *val) const
  {
    if (auto *str = dynamic_cast<StringBaseType *>(val))
    {
      return Value() == str->Value();
    }
    return false;
  }

  std::string StringType::EscapedValue() const
  {
    return StringUtils::Escape(Value());
  }

  bool KeywordType::Equals(ValType *val) const
  {
    return Value() == static_cast<KeywordType *>(val)->Value();
  }

  std::string ListType::ToString(bool readably) const
  {
    return "(" + SequenceType::ToString(readably) + ")";
  }

  SequenceType::~SequenceType()
  {
    delete m_list;
  }

  std::string SequenceType::ToString(bool readably) const
  {
    std::string str;
    auto end = m_list->cend();
    auto it = m_list->cbegin();
    if (it != end)
    {
      str += (*it)->ToString(readably);
      ++it;
    }
    for (; it != end; ++it)
    {
      str += " ";
      str += (*it)->ToString(readably);
    }
    return str;
  }

  bool SequenceType::Equals(ValType *val) const
  {
    const SequenceType *rhsSeq = static_cast<const SequenceType *>(val);
    if (Size() != rhsSeq->Size())
    {
      return false;
    }

    for (ValIter it0 = m_list->begin(),
                 it1 = rhsSeq->begin(),
                 end = m_list->end();
         it0 != end; ++it0, ++it1)
    {

      if (!(*it0)->Equals((*it1).get()))
      {
        return false;
      }
    }
    return true;
  }

  ValVec *SequenceType::EvalItems(EnvPtr env)
  {
    ValVec *result = new ValVec();
    result->reserve(m_list->size());
    for (auto &val : *m_list)
    {
      result->push_back(EVAL(val, env));
    }
    return result;
  }

  bool SequenceType::IsTrue() const
  {
    return Size() != 0;
  }

  ValPtr SequenceType::First() const
  {
    return Size() == 0 ? NilVal() : At(0);
  }

  ValPtr SequenceType::Rest() const
  {
    ValIter start = (Size() > 0 ? begin() + 1 : end());
    return ListVal(start, end());
  }

  ValPtr ListType::Eval(EnvPtr env)
  {
    if (Size() == 0)
      return ValPtr(this);

    std::unique_ptr<ValVec> items(EvalItems(env));
    auto it = items->begin();
    ValPtr op = *it;
    return APPLY(op, ListPtr(new ListType(++it, items->end())));
  }

  ValPtr ListType::Conj(ValIter argsBegin, ValIter argsEnd) const
  {
    int oldItemCount = std::distance(begin(), end());
    int newItemCount = std::distance(argsBegin, argsEnd);

    ValVec *items = new ValVec(oldItemCount + newItemCount);
    std::reverse_copy(argsBegin, argsEnd, items->begin());
    std::copy(begin(), end(), items->begin() + newItemCount);

    return ListVal(items);
  }

  std::string VectorType::ToString(bool readably) const
  {
    return '[' + SequenceType::ToString(readably) + ']';
  }

  ValPtr VectorType::Eval(EnvPtr env)
  {
    return VectorVal(EvalItems(env));
  }

  ValPtr VectorType::Conj(ValIter argsBegin, ValIter argsEnd) const
  {
    int oldItemCount = std::distance(begin(), end());
    int newItemCount = std::distance(argsBegin, argsEnd);

    ValVec *items = new ValVec(oldItemCount + newItemCount);
    std::reverse_copy(argsBegin, argsEnd, items->begin());
    std::copy(begin(), end(), items->begin() + newItemCount);

    return VectorVal(items);
  }

  bool AtomType::Equals(ValType *val) const
  {
    return m_value->Equals(val);
  }

  static std::string MakeHashKey(ValPtr key)
  {
    if (const StringType *skey = DYNAMIC_CAST(StringType, key))
    {
      return skey->ToString();
    }
    else if (const KeywordType *kkey = DYNAMIC_CAST(KeywordType, key))
    {
      return kkey->ToString();
    }
    LISP_CHECK(false, "Key must be a string or a keyword");
  }

  static HashType::Map AddToMap(HashType::Map &map, ValVec *pairs)
  {
    for (auto it = pairs->begin(); it != pairs->end(); ++it)
    {
      std::string key = MakeHashKey(*it++);
      map[key] = *it;
    }
    return map;
  }

  static HashType::Map CreateMap(ValVec *pairs)
  {
    LISP_CHECK(pairs->size() % 2 == 0, "Number of arguments must be even");

    HashType::Map map;
    return AddToMap(map, pairs);
  }

  HashType::HashType(ValVec *pairs, bool isEvaluated)
      : m_map(CreateMap(pairs)),
        m_isEvaluated(isEvaluated)
  {
  }

  HashType::HashType(ValIter begin, ValIter end, bool isEvaluated)
      : m_map(CreateMap(new ValVec(begin, end))),
        m_isEvaluated(isEvaluated)
  {
  }

  std::string HashType::ToString(bool readably) const
  {
    std::string s = "{";

    auto it = m_map.begin(), end = m_map.end();
    if (it != end)
    {
      s += it->first + " " + it->second->ToString(readably);
      ++it;
    }
    for (; it != end; ++it)
    {
      s += " " + it->first + " " + it->second->ToString(readably);
    }

    return s + "}";
  }

  bool HashType::Equals(ValType *val) const
  {
    const HashType::Map &r_map = static_cast<const HashType *>(val)->m_map;
    if (m_map.size() != r_map.size())
    {
      return false;
    }

    for (auto it0 = m_map.begin(), end0 = m_map.end(), it1 = r_map.begin();
         it0 != end0; ++it0, ++it1)
    {

      if (it0->first != it1->first)
      {
        return false;
      }
      if (!it0->second->Equals(it1->second.get()))
      {
        return false;
      }
    }
    return true;
  }

  ValPtr HashType::Eval(EnvPtr env)
  {
    if (m_isEvaluated)
    {
      return ValPtr(this);
    }

    HashType::Map map;
    for (auto it = m_map.begin(), end = m_map.end(); it != end; ++it)
    {
      map[it->first] = EVAL(it->second, env);
    }
    return HashVal(map);
  }

  ValPtr HashType::Assoc(ValIter argsBegin, ValIter argsEnd) const
  {
    LISP_CHECK(std::distance(argsBegin, argsEnd) % 2 == 0, "Assoc requires an even-sized list");
    HashType::Map map(m_map);
    return HashVal(AddToMap(map, new ValVec(argsBegin, argsEnd)));
  }

  ValPtr HashType::Dissoc(ValIter argsBegin, ValIter argsEnd) const
  {
    HashType::Map map(m_map);
    for (auto it = argsBegin; it != argsEnd; ++it)
    {
      std::string key = MakeHashKey(*it);
      map.erase(key);
    }
    return HashVal(map);
  }

  ValPtr HashType::Get(ValPtr key) const
  {
    auto it = m_map.find(MakeHashKey(key));
    return it == m_map.end() ? NilVal() : it->second;
  }

  ValPtr HashType::Keys() const
  {
    ValVec *keys = new ValVec();
    keys->reserve(m_map.size());
    for (auto it = m_map.begin(), end = m_map.end(); it != end; ++it)
    {
      if (it->first[0] == '"')
      {
        keys->push_back(StringVal(StringUtils::Unescape(it->first)));
      }
      else
      {
        keys->push_back(KeywordVal(it->first));
      }
    }
    return ListVal(keys);
  }

  ValPtr HashType::Values() const
  {
    ValVec *keys = new ValVec();
    keys->reserve(m_map.size());
    for (auto it = m_map.begin(), end = m_map.end(); it != end; ++it)
    {
      keys->push_back(it->second);
    }
    return ListVal(keys);
  }

  bool HashType::Contains(ValPtr key) const
  {
    auto it = m_map.find(MakeHashKey(key));
    return it != m_map.end();
  }

  std::string BuiltInFuncType::ToString(bool readably) const
  {
    return StringUtils::Format("#<BuiltIn '%s'>", m_name.c_str());
  }

  bool BuiltInFuncType::Equals(ValType *val) const
  {
    return this == val;
  }

  ValPtr BuiltInFuncType::Apply(ListPtr args) const
  {
    return m_func(m_name, args);
  }

  LambdaType::LambdaType(const std::vector<std::string> &bindngs, ValPtr body, EnvPtr env)
      : m_bindings(bindngs),
        m_body(body),
        m_env(env),
        m_isMacro(false) {}

  LambdaType::LambdaType(const LambdaType &that, ValPtr meta)
      : ApplicableType(meta),
        m_bindings(that.m_bindings),
        m_body(that.m_body),
        m_env(that.m_env),
        m_isMacro(that.m_isMacro) {}

  LambdaType::LambdaType(const LambdaType &that, bool isMacro)
      : ApplicableType(that.m_meta),
        m_bindings(that.m_bindings),
        m_body(that.m_body),
        m_env(that.m_env),
        m_isMacro(isMacro) {}

  LambdaType::~LambdaType() {}

  std::string LambdaType::ToString(bool readably) const
  {
    return StringUtils::Format("#<Lambda %p>", this);
  }

  bool LambdaType::Equals(ValType *val) const
  {
    return false;
  }

  ValPtr LambdaType::Apply(ListPtr args) const
  {
    try
    {
      return EVAL(m_body, MakeEnv(args));
    }
    catch (const ReturnException &e)
    {
      return e.GetValue();
    }
  }

  EnvPtr LambdaType::MakeEnv(ListPtr args) const
  {
    return EnvPtr(new Env(m_env, m_bindings, args));
  }

  std::string AtomType::ToString(bool readably) const
  {
    return "(Atom " + m_value->ToString() + ")";
  }

  ValPtr AtomVal(ValPtr value)
  {
    return ValPtr(new AtomType(value));
  }

  ValPtr NilVal()
  {
    static ValPtr t(new ConstantType("nil"));
    return ValPtr(t);
  }

  ValPtr TrueVal()
  {
    static ValPtr t(new ConstantType("true"));
    return ValPtr(t);
  }

  ValPtr FalseVal()
  {
    static ValPtr t(new ConstantType("false"));
    return ValPtr(t);
  }

  ValPtr BoolVal(bool value)
  {
    return value ? TrueVal() : FalseVal();
  }

  ValPtr IntegerVal(int64_t value)
  {
    return ValPtr(new IntegerType(value));
  }

  ValPtr IntegerVal(const std::string &token)
  {
    return ValPtr(new IntegerType(std::stoll(token)));
  }

  ValPtr StringVal(const std::string &token)
  {
    return ValPtr(new StringType(token));
  }

  ValPtr SymbolVal(const std::string &token)
  {
    return ValPtr(new SymbolType(token));
  }

  ValPtr ListVal(ValVec *list)
  {
    return ValPtr(new ListType(list));
  }

  ValPtr ListVal(std::initializer_list<ValPtr> list)
  {
    return ValPtr(new ListType(new ValVec(list)));
  }

  ValPtr ListVal(ValIter begin, ValIter end)
  {
    return ValPtr(new ListType(begin, end));
  }

  ValPtr VectorVal(ValVec *list)
  {
    return ValPtr(new VectorType(list));
  }

  ValPtr VectorVal(std::initializer_list<ValPtr> list)
  {
    return ValPtr(new VectorType(new ValVec(list)));
  }

  ValPtr VectorVal(ValIter begin, ValIter end)
  {
    return ValPtr(new VectorType(begin, end));
  }

  ValPtr HashVal(ValVec *pairs, bool isEvaluated)
  {
    return ValPtr(new HashType(pairs, isEvaluated));
  }

  ValPtr HashVal(std::initializer_list<ValPtr> pairs, bool isEvaluated)
  {
    return ValPtr(new HashType(new ValVec(pairs), isEvaluated));
  }

  ValPtr HashVal(ValIter begin, ValIter end, bool isEvaluated)
  {
    return ValPtr(new HashType(begin, end, isEvaluated));
  }

  ValPtr HashVal(const HashType::Map &map)
  {
    return ValPtr(new HashType(map));
  }

  ValPtr BuiltInVal(const std::string &name, BuiltInFuncType::ApplyFuncPtr func)
  {
    return ValPtr(new BuiltInFuncType(name, func));
  }

  ValPtr LambdaVal(const std::vector<std::string> &params, ValPtr body, EnvPtr env)
  {
    return ValPtr(new LambdaType(params, body, env));
  }

  ValPtr MacroVal(const LambdaType &lambda)
  {
    return ValPtr(new LambdaType(lambda, true));
  }

  ValPtr KeywordVal(const std::string &token)
  {
    return ValPtr(new KeywordType(token));
  }

} // namespace Lisp
